cd95f3
@@ -70,24 +70,23 @@
public class SpelCompiler implements Opcodes {
 
 	private static final Log logger = LogFactory.getLog(SpelCompiler.class);
 
+	private final static int CLASSES_DEFINED_LIMIT = 100;
+
 	// A compiler is created for each classloader, it manages a child class loader of that
 	// classloader and the child is used to load the compiled expressions.
 	private static final Map<ClassLoader, SpelCompiler> compilers =
 			new ConcurrentReferenceHashMap<>();
 
-
 	// The child ClassLoader used to load the compiled expression classes
-	private final ChildClassLoader ccl;
+	private ChildClassLoader ccl;
 
 	// Counter suffix for generated classes within this SpelCompiler instance
 	private final AtomicInteger suffixId = new AtomicInteger(1);
 
-
 	private SpelCompiler(ClassLoader classloader) {
 		this.ccl = new ChildClassLoader(classloader);
 	}
 
-
 	/**
 	 * Attempt compilation of the supplied expression. A check is
 	 * made to see if it is compilable before compilation proceeds. The
@@ -131,7 +130,6 @@
public class SpelCompiler implements Opcodes {
 	 * @return the expression call, or {@code null} if the decision was to opt out of
 	 * compilation during code generation
 	 */
-	@SuppressWarnings("unchecked")
 	private Class<? extends CompiledExpression> createExpressionClass(SpelNodeImpl expressionToCompile) {
 		// Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
 		String clazzName = "spel/Ex" + getNextSuffix();
@@ -183,9 +181,26 @@
public class SpelCompiler implements Opcodes {
 		byte[] data = cw.toByteArray();
 		// TODO need to make this conditionally occur based on a debug flag
 		// dump(expressionToCompile.toStringAST(), clazzName, data);
-		return (Class<? extends CompiledExpression>) this.ccl.defineClass(clazzName.replaceAll("/", "."), data);
+		return loadClass(clazzName.replaceAll("/", "."), data);
 	}
 
+	/**
+	 * Load a compiled expression class. Makes sure the classloaders aren't used too much
+	 * because they anchor compiled classes in memory and prevent GC.  If you have expressions
+	 * continually recompiling over time then by replacing the classloader periodically
+	 * at least some of the older variants can be garbage collected.
+	 * 
+	 * @param name name of the class
+	 * @param bytes bytecode for the class
+	 * @return the Class object for the compiled expression
+	 */
+	@SuppressWarnings("unchecked")
+	private Class<? extends CompiledExpression> loadClass(String name, byte[] bytes) {
+		if (this.ccl.getClassesDefinedCount() > CLASSES_DEFINED_LIMIT) {
+			this.ccl = new ChildClassLoader(this.ccl.getParent());
+		}
+		return (Class<? extends CompiledExpression>) this.ccl.defineClass(name, bytes);
+	}
 
 	/**
 	 * Factory method for compiler instances. The returned SpelCompiler will
@@ -269,12 +284,20 @@
public class SpelCompiler implements Opcodes {
 
 		private static final URL[] NO_URLS = new URL[0];
 
+		private int classesDefinedCount = 0;
+
 		public ChildClassLoader(ClassLoader classLoader) {
 			super(NO_URLS, classLoader);
 		}
 
+		int getClassesDefinedCount() {
+			return classesDefinedCount;
+		}
+
 		public Class<?> defineClass(String name, byte[] bytes) {
-			return super.defineClass(name, bytes, 0, bytes.length);
+			Class<?> clazz = super.defineClass(name, bytes, 0, bytes.length);
+			classesDefinedCount++;
+			return clazz;
 		}
 	}
 
